﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


namespace Nirvana
{
    /// <summary>
    /// Prefab资源池管理：用于管理Prefab资源的引用计数，控制资源AB的加载/释放内存，在Prefab资源还没销毁时，能重复创建实体，避免重复下载AB
    /// </summary>
    public class PrefabPool : CSharpSingletion<PrefabPool>
    {
        public struct PrefabPoolEvent
        {
            public PrefabPoolEvent(AssetID assetId, Action<GameObject> complete)
            {
                this.assetId = assetId;
                this.complete = complete;
            }

            public AssetID assetId;
            public Action<GameObject> complete;
        }

        private static Logger logger = LogSystem.GetLogger("PrefabPool");

        /// <summary>
        /// 保存以AB名加载的记录，以便下次直接加载
        /// </summary>
        private Dictionary<AssetID, PrefabCache> PrefabCacheAssetIDMap = new Dictionary<AssetID, PrefabCache>();
        /// <summary>
        /// 保存以Prefab资源加载的记录，以便下次直接加载
        /// </summary>
        private Dictionary<GameObject, PrefabCache> PrefabCacheObjMap = new Dictionary<GameObject, PrefabCache>();
        private Queue<PrefabPoolEvent> PrefabEventQueue = new Queue<PrefabPoolEvent>();

        private float defaultReleaseAfterFree = 30f;
        private int defaultMaxPoolCount;
        private int releaseCount;
        private float curLoadTime;
        private int curLoadingCount = 0;
        private int maxLoadingCount = 5;
        public int MaxLoadingCount
        {
            get { return maxLoadingCount; }
            set { maxLoadingCount = value; }
        }
        public float DefaultReleaseAfterFree
        {
            get
            {
                return defaultReleaseAfterFree;
            }
            set
            {
                defaultReleaseAfterFree = value;
                foreach (var p in PrefabCacheAssetIDMap)
                    p.Value.SetReleaseTimeAfterFree(value);
            }
        }

        public Dictionary<AssetID,PrefabCache> GetCacheAssetIDMap()
        {
            return PrefabCacheAssetIDMap;
        }
        /// <summary>
        /// 初始化
        /// </summary>
        protected override void Init()
        {
            //添加帧回调
            Scheduler.AddFrameListener(OnUpdate);
        }
		
        private void OnUpdate()
        {
            this.LoadOnQueue();
            //每秒检测释放未引用的资源
            if (Time.time - curLoadTime >= 1f)
            {
                this.curLoadTime = Time.time;
                this.releaseCount = 0;
                this.PrefabCacheAssetIDMap.RemoveAll(UnloadOnTime);
            }

        }

        /// <summary>
        /// 排队下载
        /// </summary>
        private void LoadOnQueue()
        {
            //int num = this.maxLoadingCount + this.PrefabEventQueue.Count / 5;//加载表中超过5个就多下载1个，加快速度
            //while (this.PrefabEventQueue.Count > 0 && num - this.curLoadingCount > 0)
            //{
            //    this.curLoadingCount++;
            //    PrefabPoolEvent _event = this.PrefabEventQueue.Dequeue();
            //    Scheduler.RunCoroutine(this.LoadPrefabASyncByLoadingCount(_event.assetId, _event.complete));
            //}

            if (this.PrefabEventQueue.Count > 0)
            {
                this.curLoadingCount++;
                PrefabPoolEvent _event = this.PrefabEventQueue.Dequeue();
                Scheduler.RunCoroutine(this.LoadPrefabASyncByLoadingCount(_event.assetId, _event.complete));
            }
        }

        public GameObject Instantiate(GameObject prefab)
        {
            if(prefab == null)
            {
                logger.LogError("Try to Instantiate a null Prefab.");
                return null;
            }
            GameObject gameObject = UnityEngine.Object.Instantiate<GameObject>(prefab);
            //添加弱引用记录，以便弱引用也引用一次prefab，当对象自毁时，自动清除当前引用计数
            //PrefabReference prefabReference = ReferenceDict.AddPrefabReferenceWeak(gameObject.GetInstanceID(), prefab);
            PrefabReferenceHolder ph = gameObject.GetOrAddComponent<PrefabReferenceHolder>();
            //ph.Set(prefabReference);
            return gameObject;
        }
        public bool Retain(GameObject prefab)
        {
            if (prefab == null)
            {
                logger.LogError("Try to Retain a null Prefab.");
                return false;
            }

            if (!this.PrefabCacheObjMap.TryGetValue(prefab, out PrefabCache prefabCache))
            {
                return false;
            }
            prefabCache.Retain();
            return true;
        }

        public void Free(GameObject prefab, bool destroy = false)
        {
            if (prefab == null)
            {
                logger.LogError("Try to free a null Prefab.");
                return;
            }

            if (!this.PrefabCacheObjMap.TryGetValue(prefab, out PrefabCache prefabCache))
            {
                logger.LogWarning("Try to free an instance {0} not allocated by this pool.", prefab.name);
                return;
            }
            prefabCache.Release();
            if (destroy && prefabCache.ReferenceCount == 0)
            {
                AssetID assetID = prefabCache.AssetID;
                GameObject gameObject = prefabCache.GetObject();
                if (gameObject != null)
                {
                    AssetManager.UnloadAsseBundle(assetID.BundleName);
                    this.PrefabCacheObjMap.Remove(gameObject);
                }
                this.PrefabCacheAssetIDMap.Remove(assetID);
            }
        }

        public void Clear()
        {
            this.PrefabCacheAssetIDMap.Clear();
            this.PrefabCacheObjMap.Clear();
            this.PrefabEventQueue.Clear();
            curLoadingCount = 0;
        }

        public void ClearAllUnused()
        {
            this.PrefabCacheAssetIDMap.RemoveAll(Unload);
            PrefabEventQueue.Clear();
			curLoadingCount = 0;
        }

        /// <summary>
        /// 加载Prefab
        /// </summary>
        /// <param name="assetID"></param>
        /// <param name="complete"></param>
        /// <param name="isSync">是否立即加载，不是的话排队，由加载队列加载</param>
        public void Load(AssetID assetID, Action<GameObject> complete, bool isSync = false)
        {
            if (!assetID.AssetName.EndsWith(".prefab"))
            {
                assetID.AssetName += ".prefab";
            }
            if (AssetManager.Simulator.SimulateAssetBundle)
            {
                Scheduler.RunCoroutine(this.LoadPrefabASync(assetID, complete));
                return;
            }
            assetID.AssetName = assetID.AssetName.ToLower();
            if (isSync)
            {
                Scheduler.RunCoroutine(this.LoadPrefabSync(assetID, complete));
                return;
            }
            if (!AssetManager.IsVersionCached(assetID.BundleName))
            {
                Scheduler.RunCoroutine(this.LoadPrefabASync(assetID, complete));
                return;
            }
            PrefabPoolEvent item = new PrefabPoolEvent(assetID, complete);
            this.PrefabEventQueue.Enqueue(item);
        }


        private IEnumerator LoadPrefabASyncByLoadingCount(AssetID assetid, Action<GameObject> cb)
        {
            WaitLoadPrefab wait = this.WaitLoadPrefab(assetid, false);
            if (wait.keepWaiting)
            {
                yield return wait;
            }
            curLoadingCount--;
            if (curLoadingCount < 0)
            {
                Debug.LogError("[PrefabPool] loadingCount is occur error " + this.curLoadingCount);
            }
            if (!string.IsNullOrEmpty(wait.Error))
            {
                logger.LogError(wait.Error);
                cb(null);
            }
            else
            {
                cb(wait.LoadedObject);
            }
            yield break;
        }

        private IEnumerator LoadPrefabASync(AssetID assetid, Action<GameObject> cb)
        {
            WaitLoadPrefab wait = this.WaitLoadPrefab(assetid, false);
            if (wait.keepWaiting)
            {
                yield return wait;
            }
            if (!string.IsNullOrEmpty(wait.Error))
            {
                logger.LogError(wait.Error);
                cb(null);
            }
            else
            {
                cb(wait.LoadedObject);
            }
            yield break;
        }

        private IEnumerator LoadPrefabSync(AssetID assetid, Action<GameObject> cb)
        {
            WaitLoadPrefab wait = this.WaitLoadPrefab(assetid,true);
            if (wait.keepWaiting)
            {
                yield return wait;
            }

            if (!string.IsNullOrEmpty(wait.Error))
            {
                logger.LogError(wait.Error);
                cb(null);
            }else
            {
                cb(wait.LoadedObject);
            }
            yield break;
        }


        private WaitLoadPrefab WaitLoadPrefab(AssetID assetid,bool isSync)
        {
            PrefabCache prefabcache;
            if(this.PrefabCacheAssetIDMap.TryGetValue(assetid,out prefabcache))
            {
                prefabcache.Retain();
                return new WaitLoadPrefab(prefabcache);
            }

            prefabcache = new PrefabCache(assetid, this.PrefabCacheObjMap);
            prefabcache.SetReleaseTimeAfterFree(this.defaultReleaseAfterFree);
            prefabcache.IsSync = isSync;
            prefabcache.Load(assetid);
            prefabcache.Retain();
            this.PrefabCacheAssetIDMap.Add(assetid, prefabcache);
            return new WaitLoadPrefab(prefabcache);
        }


        private bool UnloadOnTime(AssetID assetid,PrefabCache cache)
        {
            if (!string.IsNullOrEmpty(cache.Error))
            {
                return true;
            }

            if (cache.ReferenceCount > 0)
            {
                return false;
            }
            //1秒时间内，只释放5个资源，避免频繁释放/加载AB
            if (this.releaseCount < 5 && Time.time > cache.LastFreeTime + cache.ReleaseAfterFree)
            {
                this.releaseCount++;
                var obj = cache.GetObject();
                if (obj != null)
                {
                    this.PrefabCacheObjMap.Remove(obj);
                    AssetManager.UnloadAsseBundle(assetid.BundleName);
                }
                return true;
            }
            return false;
        }
  
        private bool Unload(AssetID assetid, PrefabCache cache)
        {
            if (!string.IsNullOrEmpty(cache.Error))
            {
                return true;
            }

            if (cache.ReferenceCount > 0)
            {
                return false;
            }

            var obj = cache.GetObject();
            if (obj != null)
            {
                this.PrefabCacheObjMap.Remove(obj);
                AssetManager.UnloadAsseBundle(assetid.BundleName);
            }
            return true;
        }
    }
}

